import cell
import pygame
import numpy as np

VIVO = 1
MUERTO = 0


def clamp(value, maxValue, minValue):
        '''
        Funcion clamp, devuelve value si value esta entre min y max, devuelve min o max si no.
        '''
        if value<minValue:
            return minValue
        if value>maxValue:
            return maxValue
        return value


class Matrix():
    """Creo una matriz que sera una lista de listas
    la lista inicial tendra en cada posicion las filas
    de la matriz:
    Ejemplo de matriz 4xn
        [fila1, fila2, fila3, fila4]
    Donde fila_i es una lista de longitud n"""
    
#########################
# PRIVATE    

    def __init__(self, rows, cols, sizeCell):
        self.cols = cols
        self.rows = rows
        self.t = 0
        # Inicializo matriz
        self.matrix = []
        self.group = []
        self.thresh = 0
        
        for i in range(rows):
            # Creo una fila nuevo
            row = []
            
            for j in range(cols):
                # Lleno la fila con 0 en cada celda
                celda = cell.Cell(pos=(j,i), porosidad=1, size=sizeCell)
                row.append(celda)
                self.group.append(celda)
            
            self.matrix.append(row)
        self.sprite_group = pygame.sprite.Group( self.group )

    def __repr__(self):
        
        ret = ''
        for i in range(self.rows):
            ret += 'Fila %s = %s \n'%(i+1,self.matrix[i])
        
        return ret

#########################
# GETERS
        
    def get_value(self, row, col):
        ''' Devuelve el valor de la celda en (row,col)'''
        
        # Si se pide una celda fuera de la matriz
        if self.rows < row or row < 1 or self.cols < col or col < 1:
            
            # Por defecto devuelvo el valor de una celda muerta
            return MUERTO
        
        return (self.matrix[row-1][col-1]).get_pressure()
    
    def get_cell(self, row, col):
        if self.rows < row or row < 1 or self.cols < col or col < 1:
            # Por defecto devuelvo el valor de una celda muerta
            return cell.Cell()
        return self.matrix[row-1][col-1]
    
    def getCellsBB(self,box):
        ''' Extrae la submatriz completamente contenida en BB.
            Esta funcion requiere que self.matrix sea un un array de numpy.
            Suponiendo que box es [left, top, rigth, bottom]'''
        left,top,right,bottom = map(int,np.ceil(np.array(box)) )
        
        left = clamp(left,self.cols,0)
        right = clamp(right,self.cols,0)
        top = clamp(top,self.rows,0)
        bottom = clamp(bottom,self.rows,0)

        mat = [[(self.matrix[x][y]).get_pressure() for y in range(left,right)] for x in range(top,bottom)]

        return np.array(mat)
        
    def vecinos(self, row, col):
        ''' Devuelve una lista con los vecinos. Es una lista de celdas'''
        ret = []
        
        # Obtengo vecinos vivos
        for i in range(row-1, row+2):
            for j in range(col-1, col+2):
                if (i, j) != (row,col) :
                    ret.append(self.get_cell(i,j))
        
        return ret
        
    # Vecindad de Neumann, norte, sur, este, oeste
    def vecinos_neumann(self, row, col):
        ''' Devuelve una lista con los vecinos. Es una lista de celdas'''
        ret = []
        i = row
        j = col 
        # Obtengo vecino del Norte
        ret.append(self.get_cell(i-1,j))
        # Obtengo vecino del Sur
        ret.append(self.get_cell(i+1,j))
        # Obtengo vecino del Este
        ret.append(self.get_cell(i,j+1))
        # Obtengo vecino del Oeste
        ret.append(self.get_cell(i,j-1))
        
        return ret

#########################
# SETERS
       
    def setCellPressure(self, fila, col, val):
        self.get_cell(fila, col).pressure = val
        self.get_cell(fila, col).new_pressure = val
        self.get_cell(fila, col).set_color(val)
        
    def setMatrixValues(self, value):
        for x in range(1, self.rows+1):
            for y in range(1, self.cols+1):
                self.setCellPressure(x, y, value)
        
#########################
# UPDATERS
    
    def rules_pressure(self):
        thresh= []
        for i in range(1, self.rows+1):
            for j in range(1, self.cols+1):
                
                thresh.append(self.get_value(i,j))
                ''' if cell is not update then update '''
                if self.get_cell(i,j).t != self.t:
                    self.get_cell(i,j).t= self.t
                    self.get_cell(i,j).update()
                    
                
                ''' loop trhought all the neighbours'''    
                vecinos = self.vecinos_neumann(i,j)
                total_vecinos = float(len(vecinos))
                
                for vecino in vecinos:
                    ''' if the cell cannot have flow trhough it, ignore '''
                    if not vecino.allow_flow:
                        continue
                    ''' if neighbor is not updated then update '''    
                    if vecino.t != self.t:
                        vecino.t= self.t
                        vecino.update()
                        
                    # Calculo la diferencia de presion
                    dpres = self.get_value(i,j) - vecino.get_pressure()
                
                    '''Calculamos el flujo como una fraccion de la diferencia de presion.
                    0.12 es un numero magico que indica la fluidez del medio. Un valor muy chico
                    hace que la presion se propague mas lentamente.
                    Usamos la funcion clamp para evitar valores negativos en el nodo.
                    Se hace que el flujo no produsca una presion negativa'''
                    flow = self.get_cell(i,j).porosidad*dpres
                    maxFlow = self.get_value(i,j)/total_vecinos
                    minFlow = -vecino.get_pressure()/total_vecinos
                                        
                    # Prevent flows bigger thna actual pressure
                    flow=clamp(flow,maxFlow,minFlow)
                     
                    if i==-2 and j==-2:
                        print "presion actual",self.get_value(i,j)
                        print "flujo",flow
                        print "presion nueva sin actualizar", self.get_cell(i,j).new_pressure 
                  
                    '''Actualizamos la presion (usando new_pressure en lugar de 
                    pressure) substrayendo presion del nodo actual y agregandola
                     a su vecino. Un flujo negativo implica que la presion del 
                     nodo actual aumenta y la del vecino disminuye.'''
                    self.get_cell(i,j).new_pressure -= flow
                    vecino.new_pressure += flow
        self.t  +=  1
        #print "Max Pressure = %s - Matrix (%s,%s): "%( max(thresh),self.rows, self.cols)
        self.thresh = max(thresh)/float(self.rows*self.cols)
        return self.t
    
    def update(self):
        '''Metodo para elegir la forma de actualizacion.'''
        
        self.rules_pressure()

########
# Pseudo Unitest
def test():

    pygame.init
    
    rows = 5
    cols = 5
    screen = pygame.display.set_mode((rows*20, cols*20))
    
    background = pygame.Surface(screen.get_size())
    background = background.convert()
    background.fill((0,0,200))
    background_rect = background.get_rect()    

    matrix = Matrix(rows,cols)
    matrix.setCellPressure(2,2,1)
    print matrix.getCellsBB([0,0,rows,cols])
    for i in xrange(0,4):
        matrix.rules_pressure()
        print matrix.getCellsBB([0,0,rows,cols])
        raw_input("next?")
        
